<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .blue {
            background: blue;
        }
        
        .red {
            background: red;
        }
    </style>
</head>

<body>

    <p>一秒钟后更新</p>
    <p><b>原来位置:</b>1,2,3</p>
    <p><b>变化后位置应该为:</b>1,2</p>
    <p><b>但是，4没有被程序命中，所以跳过更新</p>
  <p>背景变红的是更新过的</p>
  <div id="app"></div>
  <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
  <script>
    const {
      ref,
      effect
    } = VueReactivity;

    const Text = Symbol(); //文本类型
    const Comment = Symbol(); //注释文本类型
    const Fragment = Symbol(); //Fragment类型
    function createRenderer(options) {
      const {
        createElement,
        setElementText,
        insert,
        pathProps,
        createText,
        setText,
        createComment

      } = options;

      function mountElement(vnode, container, anchor) {
        //给vnode。el挂载真实的DOM元素
        const el = vnode.el = createElement(vnode.type)

        if (typeof vnode.children == "string") {
          setElementText(el, vnode.children);
        } else if (Array.isArray(vnode.children)) {
          vnode.children.forEach(child => {
            patch(null, child, el);
          });
        }

        if (vnode.props) {
          for (const key in vnode.props) {
            pathProps(el, key, null, vnode.props[key]);

          }
        }
        insert(el, container, anchor);
      }

      function patchChildren(n1, n2, container) {
        //新节点是不是文本节点
        if (typeof n2.children === "string") {
          if (Array.isArray(n1.children)) {
            n1.children.forEach((c) => unmounted(c))
          }
          //如果新节点是文本，就设置文字
          setElementText(container, n2.children);
        } else if (Array.isArray(n2.children)) {
          //完全移除简单算法的代码
          //以下是双端算法的代码
          patchKeyedChildren(n1, n2, container);

        } else {
          //能跑到这里说明新节点不存在
          //但是旧节点还是存在三种情况
          if (Array.isArray(n1.chidren)) {
            n1.chidren.forEach(c => unmounted(c))
          } else if (typeof n1.chidren === "string") {
            setElementText(container, "")
          }
          //如果没有旧节点，什么都不用做
        }
      }

      function patchKeyedChildren(n1, n2, container) {
        const oldChildren = n1.children;
        const newChildren = n2.children;

        //四个索引值
        let oldStartIdx = 0;
        let oldEndIdx = oldChildren.length - 1;

        let newStartIdx = 0;
        let newEndIdx = newChildren.length - 1;

        //四个索引指向的真实vNode节点
        let oldStartVNode = oldChildren[oldStartIdx];
        let oldEndVNode = oldChildren[oldEndIdx];

        let newStartVNode = newChildren[newStartIdx];
        let newEndVNode = newChildren[newEndIdx];
        //暂时不用while，通过修改demo的vnode来重现每一个步骤
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
          //这里的步骤，对应书本上面的对比步骤
          if (!oldStartVNode) {
            oldStartVNode = oldChildren[++oldStartIdx]
          } else if (!oldEndVNode) {
            oldEndVNode = newChildren[--oldEndIdx]; //为什么这里是newChilden?搞不懂
          } else if (oldStartVNode.key === newStartVNode.key) {
            console.warn("命中步骤1===========")
            //先更新内容
            patch(oldStartVNode, newStartVNode, container);
            //因为位置（都是尾部）一样，所以不需要位移

            //完成后，需要更新索引值，之前的以下两行代码是错的，这里更正了
            oldStartVNode = oldChildren[++oldStartIdx];
            newStartVNode = newChildren[++newStartIdx];
            //步骤1
          } else if (oldEndVNode.key === newEndVNode.key) {
            console.warn("命中步骤2===========")
            //步骤2
            //先更新内容
            patch(oldEndVNode, newEndVNode, container);
            //因为位置（都是尾部）一样，所以不需要位移

            //完成后，需要更新索引值
            //之前的以下两行代码是错的，这里更正了
            oldEndVNode = oldChildren[--oldEndIdx];
            newEndVNode = newChildren[--newEndIdx];


            //为了重现单一步骤，先注释其他步骤的代码
          } else if (oldStartVNode.key === newEndVNode.key) {
            console.warn("命中步骤3===========")
            //步骤3
            //先更新内容
            patch(oldStartVNode, newEndVNode, container);
            //再位移
            insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);
            //完成后，需要更新索引值
            //之前的以下两行代码是错的，这里更正了
            oldStartVNode = oldChildren[++oldStartIdx];
            newEndVNode = newChildren[--newEndIdx];

          } else if (oldEndVNode.key === newStartVNode.key) {
            console.warn("命中步骤4===========")
            //步骤4
            //先更新内容
            patch(oldEndVNode, newStartVNode, container);
            //再位移
            insert(oldEndVNode.el, container, oldStartVNode.el);
            //完成后，需要更新索引值
            //之前的以下两行代码是错的，这里更正了
            oldEndVNode = oldChildren[--oldEndIdx];
            newStartVNode = newChildren[++newStartIdx];

          } else {
            //兜底非理想状态
            //遍历旧的节点，视图找到与【newStartVnode】①拥有一样key的节点
            const idxInOld = oldChildren.findIndex(node => node.key === newStartVNode.key);
            console.log("idxInOld", idxInOld, 'oldChildren', oldChildren, "newChildren", newChildren)
            if (idxInOld > 0) {
              const vnodeToMove = oldChildren[idxInOld];
              //先打补丁
              patch(vnodeToMove, newStartVNode, container);
              //简vnodeTovmove。el移到oldStartnode.el之前
              console.log("vnodeToMove.el", vnodeToMove, "oldStartVNode.el", oldStartVNode)
              insert(vnodeToMove.el, container, oldStartVNode.el);
              //indexInOld对应的真实节点已经移除到别处，因此设置undefined!!
              oldChildren[idxInOld] = undefined;


            } else {
              console.log("新增=========")
              //将newStartVNode作为新节点挂载【头部】②节点
              patch(null, newStartVNode, container, oldStartVNode.el)
            }
            newStartVNode = newChildren[++newStartIdx];

          }


        }


        //while后，都没有匹配到，兜底处理
        //旧节点已经没有了，新节点依旧还有
        //说明存在新增元素
        if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {
          for (let i = newStartIdx; i <= newEndIdx; i++) {
            patch(null, newChildren[i], container, oldStartVNode.el);
          }
        }

        else if (newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {
          //移除
          for (let i = oldStartIdx; i <= oldEndIdx; i++) {
            unmounted(oldChildren[i]);
          }
        }


      }
      /**
       * n1 旧节点
       * n2 新节点 
       **/
      function patchElement(n1, n2) {
        const el = n2.el = n1.el; //为patchChildren服务,只是这个在这里处理合适吗？？？！！！不应该再patchChildren那里么
        const oldProps = n1.props;
        const newProps = n2.props;
        //更新props
        for (const key in newProps) {
          if (newProps[key] != oldProps[key]) {
            pathProps(el, key, oldProps[key], newProps[key])
          }
        }
        //移除新props不包括的就props
        for (const key in oldProps) {
          if (!key in newProps) {
            patchProps(el, key, oldProps[key], null)
          }
        }
        //更新children
        patchChildren(n1, n2, el);
      }
      //添加第四个参数，就是锚点元素
      function patch(n1, n2, container, anchor) {
        //类型不一样，就卸载旧的先
        if (n1 && n1.type != n2.type) {
          unmounted(n1);
          n1 = null;

        }
        //n1不存在，意味着是挂载，则调用mountElement函数
        const {
          type
        } = n2;
        console.log("n2 type", type)
        if (typeof type === "string") {
          if (!n1) {
            mountElement(n2, container, anchor);
          } else {
            //n1存在,意味着更新
            patchElement(n1, n2);

          }
        } else if (type === Text) {
          //没有旧节点
          if (!n1) {
            const el = n2.el = createText(n2.children);
            insert(el, container)
          } else {
            const el = n2.el = n1.el;
            if (n2.children !== n1.children) {
              setText(el, n2.children);
            }
          }

        } else if (type === Comment) {

          //没有旧节点
          if (!n1) {
            const el = n2.el = createComment(n2.children);
            insert(el, container)
          } else {
            const el = n2.el = n1.el;
            if (n2.children !== n1.children) {
              setText(el, n2.children);
            }
          }
        } else if (type === Fragment) {
          if (!n1) {
            //不存在旧节点直接挂载
            n2.children.forEach(c => patch(null, c, container));
          } else {
            //如果就解决点处在，只需要更新Fragment的children即可，
            //这个需要在卸载函数里面特殊处理
            patchChildren(n1, n2, container)
          }
        } else if (typeof type === "object") {

        } else if (typeof type === "xxx") {

        }

      }
      //卸载函数
      function unmounted(vnode) {
        if (vnode.type === Fragment) {
          vnode.children.forEach(c => unmounted(c))
          return;
        }
        //根据vnode获取要卸载的真实dom元素
        const el = vnode.el;
        const parent = el.parentNode; //通过真实的dom父级来卸载，而不是vnode
        if (parent) {
          parent.removeChild(el);
        };
      }
      //客户端渲染
      function render(vnode, container) {
        if (vnode) {
          //新的vnode存在，将其于旧的vnode一起传给patch函数，进行打补丁
          patch(container._vnode, vnode, container);
        } else {
          if (container._vnode) {
            //container.innerHTML="";
            unmounted(container._vnode);
          }
        }

        //将vnode存储到container._vnode下，后续渲染中的旧vnode
        container._vnode = vnode;
      }
      //服务器渲染
      function hydrate(vnode, container) {

      }
      return {
        render,
        hydrate
      }
    }

    function shouldSetAsProps(el, key, value) {
      //特殊处理
      if (key === 'form' && el.tagName === 'INPUT') {
        return false;
      }
      return key in el;
    }
    //使用============
    const renderer = createRenderer({
      createElement(tag) {
        return document.createElement(tag);
      },
      setElementText(el, text) {
        el.textContent = text;
      },
      insert(el, parent, anchor = null) {
        parent.insertBefore(el, anchor);
      },
      createText(text) {
        return document.createTextNode(text);
      },
      setText(el, text) {
        el.nodeValue = text;
      },
      createComment(text) {
        return document.createComment(text);
      },
      pathProps(el, key, prevValue, nextValue) {
        if (/^on/.test(key)) {
          const invokers = el._vei || (el._vei = {});
          let invoker = invokers[key];
          const name = key.slice(2).toLowerCase();
          console.log("点击事件", name, nextValue)
          if (nextValue) {
            if (!invoker) {
              invoker = el._vei[key] = (e) => {
                //触发事件小于绑定事件，不执行任何回调
                //e.timeStamp 不同浏览器，可能是精度时间，也可能不是精度时间
                if (e.timeStamp <= invoker.attacked) {
                  return;
                }
                if (Array.isArray(invoker.value)) {
                  invoker.value.forEach(fn => {
                    fn(e);
                  });
                } else {
                  invoker.value(e);
                }

              }
              invoker.value = nextValue;
              invoker.attacked = performance.now();
              el.addEventListener(name, invoker);
            } else {
              invoker.value = nextValue;
            }

          } else if (invoker) {
            el.removeEventListener(name, invoker);
          }
        } else if (key === 'class') {
          el.className = nextValue || "";
        } else if (shouldSetAsProps(el, key, nextValue)) {
          //获取属性对应的值的类型
          const type = typeof el[key];
          console.log("key", key, typeof type)
          if (typeof type === 'boolean' && nextValue === "") {
            el[key] = true;
          } else {
            el[key] = nextValue;
          }
        } else {
          //el[key] = nextValue; //dom props优先
          el.setAttribute(key, nextValue);
        }
      }
    });

    function normalizeClass(arr) {
      let t = "";
      for (let k = 0; k < arr.length; k++) {
        let item = arr[k];
        if (typeof item === 'string') {
          t += " " + item;
        } else if (typeof item === "object") {
          for (let key in item) {
            if (item[key]) {
              t += " " + key;
            }
          }
        }
      }
      return t;
    }

    const bol = ref(true);
    console.log("Text", Text)
    //一定要研究虚拟dom位置的差异
    effect(() => {
      const vnode = bol.value ? {
        type: "div",
        key: 1,
        children: [{
          type: "p",
          key: 1,
          children: "p-1",
          props: {
            class: "blue"
          }
        }, {
          type: "p",
          key: 2,
          children: "p-2",
          props: {
            class: "blue"
          }

        }, {
          type: "p",
          key: 3,
          children: "p-3",
          props: {
            class: "blue"
          }
        }]
      } : {
        type: "div",
        key: 1,
        children: [{
          type: "p",
          key: 1,
          children: "p-1",
          props: {
            class: "red"
          }


        }, {
          type: "p",
          key: 2,
          children: "p-2",
          props: {
            class: "red"
          }

        }]
      }
      const app = document.querySelector("#app");

      //首次渲染
      renderer.render(vnode, app);
    })
    //用来更新vnode渲染
    setTimeout(() => {
      console.log("触发更新!!!!!!!")
      bol.value = false;
    }, 1500)
  </script>
</body>

</html>